<?php
/**
 * texo
 *
 * text format processor
 *
 * @version:	0.1a
 * @package:	global
 * @author:	alex_ez
 * @created:	23.02.2009 17:51:30
 *
 * @copyright:	(c) 2009 alex <qfox@ya.ru> yaroshevich aka alex_ez. All rights reserved.
 */

// html processign based on tidy

// php -//- on inner mechs (like highlight_string, highlight_file, token_get_all, token_name)

// typography -//- on Jevix. so include it

final class texo
{
	static public function plain( $t )
	{
		return self::run( $t, "striptags wrap entities" );
	}
	
	static public function unplain( $t )
	{
		return self::run( $t, "unentities unwrap" );
	}
	
	static public function paragraph( $t )
	{
		return "<p>".self::run( $t, "striptags r|\r r|\n\n|\\n entities wrap r|\n|<br/> r|\\n|</p><p>" )."</p>";
	}
	
	static public function typo( $t )
	{
		$j = self::getJevix( );
		
		// Переменная, в которую будут записыватся ошибки
		$errors = null;
		
		// Парсим
		$res = $j ->parse( $t, $errors );
//		print "результат: \n-------------\n$res";
//		if( $errors )
//			print "\n-------------\nошибки: \n".print_r( $errors, 1 );
		
		return $res;
	}
	
	static public function getJevix( )
	{
		static $j;
		if( $j instanceof Jevix ) return $j;
		
		$j = new Jevix();
		//Конфигурация
		
		// 1. Устанавливаем разрешённые теги. (Все не разрешенные теги считаются запрещенными.)
		$j ->cfgAllowTags( array( 'a', 'img', 'i', 'b', 'u', 'em', 'strong', 'nobr', 'li', 'ol', 'ul', 'sup', 'abbr', 'pre', 'acronym', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'adabracut', 'br', 'code' ) );
		
		// 2. Устанавливаем коротие теги. (не имеющие закрывающего тега)
		$j ->cfgSetTagShort( array( 'br','img' ) );
		
		// 3. Устанавливаем преформатированные теги. (в них все будет заменятся на HTML сущности)
		$j ->cfgSetTagPreformatted( array( 'pre' ) );
		
		// 4. Устанавливаем теги, которые необходимо вырезать из текста вместе с контентом.
		$j ->cfgSetTagCutWithContent( array( 'script', 'object', 'iframe', 'style' ) );
		
		// 5. Устанавливаем разрешённые параметры тегов. Также можно устанавливать допустимые значения этих параметров.
		$j ->cfgAllowTagParams( 'a', array( 'title', 'href' ) );
		$j ->cfgAllowTagParams( 'img', array( 'src', 'alt' => '#text', 'title', 'align' => array( 'right', 'left', 'center' ), 'width' => '#int', 'height' => '#int', 'hspace' => '#int', 'vspace' => '#int' ) );
		
		// 6. Устанавливаем параметры тегов являющиеся обязяательными. Без них вырезает тег оставляя содержимое.
		$j ->cfgSetTagParamsRequired( 'img', 'src' );
		$j ->cfgSetTagParamsRequired( 'a', 'href' );
		
		// 7. Устанавливаем теги которые может содержать тег контейнер
		//    cfgSetTagChilds($tag, $childs, $isContainerOnly, $isChildOnly)
		//       $isContainerOnly : тег является только контейнером для других тегов и не может содержать текст (по умолчанию false)
		//       $isChildOnly : вложенные теги не могут присутствовать нигде кроме указанного тега (по умолчанию false)
		$j ->cfgSetTagChilds( 'ul', 'li', true, true );
		
		// 8. Устанавливаем атрибуты тегов, которые будут добавлятся автоматически
		$j ->cfgSetTagParamsAutoAdd( 'a', array( 'rel' => 'nofollow' ) );
		$j ->cfgSetTagParamsAutoAdd( 'img', array( 'width' => '300', 'height' => '300' ) );
		
		// 9. Устанавливаем автозамену
		$j ->cfgSetAutoReplace( array( '+/-', '+-', '(c)', '(r)' ), array( "\xc2\xb1", "\xc2\xb1", "\xc2\xa9", "\xc2\xae" /*, "\0\xb1", "\0\xa9", "\0\xae"*/ ) );
		
		// 10. Включаем или выключаем режим XHTML. (по умолчанию включен)
		$j ->cfgSetXHTMLMode( true );
		
		// 11. Включаем или выключаем режим замены переноса строк на тег <br/>. (по умолчанию включен)
		$j ->cfgSetAutoBrMode( true );
		
		// 12. Включаем или выключаем режим автоматического определения ссылок. (по умолчанию включен)
		$j ->cfgSetAutoLinkMode( true );
		
		// 13. Отключаем типографирование в определенном теге
		$j ->cfgSetTagNoTypography( 'code' );
		
		return $j;
	}
	
	static public function replace( $t, $f, $to = null ) { return self::r( $t, $f, $to ); }
	static public function r( $t, $f, $to = null )
	{
		if( is_array( $f ) && $to === null )
			return str_replace( array_keys( $f ), array_values( $f ), $t );
		
		$to = is_null( $to )?'':($to==='\s'?' ':$to);
		return str_replace( $f, $to, $t );
	}
	
	static public function j( $d, $t = null )
	{
		return join( $d, $t );
	}
	
	static public function lowercase( $t )
	{
		return mb_strtolower( $t );
	}
	
	static public function uppercase( $t )
	{
		return mb_strtoupper( $t );
	}
	
	static public function titlecase( $t )
	{
		return mb_convert_case( $t, MB_CASE_TITLE );
	}
	
	static public function wrap( $t )
	{
		return wordwrap( str_replace( "\n", "\n\n", $t ) );
	}

	static public function unwrap( $t )
	{
		return str_replace( array( '    ', '   ', '  ' ), " ",
			str_replace( array( '  ', "\x00\x01" ), array( ' ', "\n" ),
			str_replace( array( "\n\n", "\n" ), array( "\x00\x01", " " ), $t ) ) );
	}

	static public function acronyme( $t )
	{
		if(!$t) return '';
		
		$abbr = '';
		foreach( preg_split( "/[\s,;-]+/u", $t ) as $i )
			$abbr .= mb_substr( $i, 0, 1 );
		return mb_strtoupper( $abbr );
	}

	static public function entities( $t ) { return self::escape( $t ); }
	static public function escape( $t )
	{
		return htmlentities( $t, ENT_QUOTES, 'utf-8' );
	}
	
	static public function unentities( $t ) { return self::unescape( $t ); }
	static public function unescape( $t )
	{
		return html_entity_decode( $t, ENT_QUOTES, 'utf-8' );
	}
	
	static public function cleanup( $t )
	{
		$r = $t;
		
		// cleanup duplicates
		preg_match_all( '/(<(\/?)([\w]+)(| [^>]*)>)/ui', $r, $ms, PREG_SET_ORDER );
		$_unify = array( 'em' => 'i', 'strong' => 'b', 'strike' => 's' );
		while( list(,$m) = each($ms) )
		{
			switch( strtolower( $m[3] ) )
			{
			case 'br': $r = str_replace( $m[0], '<br/>', $r ); break;
			
			case 'table': case 'tbody': case 'th': case 'tr': case 'td':
				$m[3] = strtolower( $m[3] );
				$m[3] = isset( $_unify[$m[3]] ) ? $_unify[$m[3]] : $m[3];
				$r = str_replace( $m[0], vsprintf( '<%3$s%4$s>', $m ), $r );
			break;
			
			case 'em': case 'strong': case 'strike':
			case 'i': case 'b': case 'b':
			case 'ul': case 'ol': case 'li':
				$m[3] = strtolower( $m[3] );
				$m[3] = isset( $_unify[$m[3]] ) ? $_unify[$m[3]] : $m[3];
				$r = str_replace( $m[0], vsprintf( '<%3$s%4$s>', $m ), $r );
			break;
			
			//case 'img': $t = str_replace( $m[0], '<img %s/>', $t ); break;
			default:
//			if( strpos( 'em,strong,strike', $m[2] ) === false ) break;
			$r = str_replace( $m[0], '', $r );
			}
		}
		
		// cleanup duplicates
		preg_match_all( '/(<([\w]+)><\\2>)(.*?)(<\/\\2><\/\\2>)/ui', $r, $ms, PREG_SET_ORDER );
		while( list(,$m) = each($ms) )
//		foreach( $ms as $m )
		{
			if( strpos( $m[3], '<' ) !== false )
			{
				$starts = preg_match_all( '/<([\w]+)(| [^>]*)>/ui', $m[3], $_ms, PREG_SET_ORDER );
				$ends = preg_match_all( '/<\/([\w]+)>/ui', $m[3], $_ms2, PREG_SET_ORDER );
				if( $starts != $ends ) continue;
				$i = preg_match( '/^[^<]+<\/([\w]+)>/ui', $m[3], $_ms );
				if( $i != 0 ) continue;
				preg_match_all( '/(<([\w]+)><\\2>)(.*?)(<\/\\2><\/\\2>)/ui', $m[3], $_md, PREG_SET_ORDER );
				while(list(,$_m)=each($_md)) array_push( $ms, $_m );
			}
			
			$r = str_replace( $m[0], vsprintf( '<%3$s>%4$s</%3$s>', $m ), $r );
		}
		
		// uncomplshed primals (like i, b, s) - '</b><b>' to ''
		$r = preg_replace( '/(<\/(i|b|s)><\\2>)/ui', '', $r );
		
		// remove overspaces between li, tr, and td 
		$r = preg_replace( '/(<\/(li)>\s+<\\2>)/ui', '</\\2><\\2>', $r );
		$r = preg_replace( '/(<(\/?)(table|tbody|th|tr|td)>\s+<(\/?)(table|tbody|th|tr|td)>)/ui', '<\\2\\3><\\4\\5>', $r );
		$r = preg_replace( '/(<(\/?)(table|tbody|th|tr|td)>\s+<(\/?)(table|tbody|th|tr|td)>)/ui', '<\\2\\3><\\4\\5>', $r );
		
		// empty
		//preg_match_all( '/(<([\w]+)(| [^>]*)>)(<\/\\2>)/ui', $t, $m, PREG_SET_ORDER );
		//preg_match_all( '/(<\/?)(\w+)([^>]*>)/', $t, $m );
		//var_dump( $ms );
		
		return $r;
	}
	
	static public function latinate( $t, $ignored = null )
	{
		if( !is_string( $t ) ) return '';
		
		//todo: добавить проверку на кодировку
		
		$search0 = array(  'й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш',  'щ',   'з', 'х', 'ъ', 'ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э', 'я',  'ч',  'с', 'м', 'и', 'т', 'ь', 'б', 'ю'  );
		$replace0 = array( 'j', 'c', 'u', 'k', 'e', 'n', 'g', 'sh', 'sch', 'z', 'h', '',  'f', 'y', 'v', 'a', 'p', 'r', 'o', 'l', 'd', 'j', 'e', 'ya', 'ch', 's', 'm', 'i', 't', '',  'b', 'yu' );
		$search1 = array(  'Й', 'Ц', 'У', 'К', 'Е', 'Н', 'Г', 'Ш',  'Щ',   'З', 'Х', 'Ъ', 'Ф', 'Ы', 'В', 'А', 'П', 'Р', 'О', 'Л', 'Д', 'Ж', 'Э', 'Я',  'Ч',  'С', 'М', 'И', 'Т', 'Ь', 'Б', 'Ю'  );
		$replace1 = array( 'j', 'c', 'u', 'k', 'e', 'n', 'g', 'sh', 'sch', 'z', 'h', '',  'f', 'y', 'v', 'a', 'p', 'r', 'o', 'l', 'd', 'j', 'e', 'ya', 'ch', 's', 'm', 'i', 't', '',  'b', 'yu' );
		$search2 = array(  ' ', '№' );
		$replace2 = array( '_', '#' );
		
		$search = array_merge( $search0, $search1, $search2 );
		$replace = array_merge( $replace0, $replace1, $replace2 );
		
		$t = str_replace( $search, $replace, $t );
		
		$ignored = is_null( $ignored ) ? '' : preg_quote($ignored,'/');
		$t = preg_replace( '/[^0-9a-z\_\-'.$ignored.']+/i', '', $t );
		$t = preg_replace( '/_+/', '-', $t );
		
		return $t;
	}

	static public function softcut( $string, $maxlength, $end = null )
	{
		if( mb_strlen( $string ) < $maxlength ) return $string;
		if( is_null( $end ) ) $end = '...';
		$string = mb_substr( $string, 0, mb_strrpos( mb_substr( $string, 0, $maxlength - 3 ), ' ', 3 ) );
		return $string.$end;
	}
	
	// bogus
	static public function stags( $t, $p = null ) { return self::striptags( $t, $p ); }
	static public function striptags( $t, $p = null )
	{
		return strip_tags( $t, $p );
	}
	
	// tidyclean
	static public function tidyclean( $t )
	{
		$tidy = new tidy( );
		$config = array( 'indent' => false, 'output-xhtml' => true, 'show-body-only' => true, 'input-xml' => false, 'show-errors' => 0, 'wrap' => 0 );
		$tidy ->parseString( $t, $config, 'utf8' );
		
		$tidy ->cleanRepair( );
		
		return $tidy ->value;
	}
	
	
	/**
	 * process function
	 *
	 * very usable for chains of text processing.
	 * for example, we must convert strip tags and, after it, html entities.
	 * we can do this:
	 * php: text::entities( texo::stags( $myText ) );
	 * or just do that:
	 * php: texo::run( $myText, "striptags entities" );
	 *
	 * @param	string	srouce text
	 * @param	string	processors list divided by spaces
	 * @return	string 	processed text
	 * 
	 */
	static public function run( $t, $h )
	{
		if( !is_string( $t ) || !mb_strlen( $t ) ) return '';
		if( !is_string( $h ) || !mb_strlen( $h ) ) return $t;
		
		$r = $t;
		$h = explode( ' ', $h );
		while( list(,$m) = each( $h ) )
		{
			// parse params
			$ps = array( );
			if( strpos( $m, '|' ) )
			{
				$ps = explode( '|', $m );
				$m = array_shift( $ps );
			}
			array_unshift( $ps, $r );
			$m = array(__class__,trim( $m ));
//			var_dump( join( '::', $m ), $ps ); echo "<hr/>";
			// call
			if( is_callable( $m ) ) //strpos( ',wrap,entities,striptags', $m ) !== false ) )
				$r = call_user_func_array( $m, $ps );
		}
		fb::log( $r, 'r' );
		
		return $r;
	}
	
	
	/**
	 * checkers
	 *
	 */
	static private $_regexps = array(
		'host' => '([a-z0-9\\-]+\\.)*([a-z0-9][a-z0-9\\-]+\\.)([a-z]{2}|info|name|com|net|org|biz|tel|aero)',
		'email' => '[a-z0-9\\-\\_\\.]+@([a-z0-9\\-]+\\.)*([a-z0-9][a-z0-9\\-]+\\.)([a-z]{2}|info|name|com|net|org|biz|tel|aero)',
		);
	
	static public function isEMail( $t )
	{
		if( !is_string( $t ) ) return false;
		$r = !!preg_match( '/^'.self::$_regexps['email'].'$/i', $t, $m );
		return $r;
	}
	
	static public function isDomain( $t )
	{
		if( !is_string( $t ) ) return false;
		return !!preg_match( '/^'.self::$_regexps['host'].'$/i', $t, $m );
	}

	static public function isURL( $t )
	{
		if( !is_string( $t ) ) return false;
		return !!preg_match( '@^(https?://)?'.self::$_regexps['host'].'(/?[a-zа-я0-9\\-\\_\\.\\/\\?#]+)$@iu', $t, $m );
	}

	static public function isPhone( $t )
	{
		return locale_phones::instance( ) ->validate( $t );
	}
	
	
	static public function getRegExp( $k )
	{
		if( isset( self::$_regexps[$k] ) )
			return self::$_regexps[$k];
	}

	/**
	 *
	 */
	static public function generate( $what, $how = null )
	{
		switch( $what )
		{
			case 'hash':
				$salt = isset( $how['salt'] ) ? $how['salt'] : sprintf( '%5.8f', microtime(1) );
				$r = str_replace( array( '=', '/', '+' ), array( '', 'xR', '2j' ), base64_encode(md5($salt,1)) );
				break;
			default:
				$r = 'empty-query';
		}
		return $r;
	}
	
	
	
	
	static public function translate( $s, $ss = null, $n = null )
	{
		if( empty( $s ) ) return '';
		
		$r = !is_null( $ss ) ? ngettext($s, $ss, $n) : gettext( $s );
		
		if( $r == $s )
		{
			$guess = mb_strtolower( $s );
			$guessr = gettext( $guess );
			if( $guessr != $guess )
				$r = $guessr;
		}
		
		if( $r == $s && ( strpos( $s, ' ' ) !== false || strpos( $s, '_' ) !== false ) )
		{
			$r = '';
			$s = explode( ' ', str_replace( '_', ' ', $s ) );
			foreach( $s as $v )
				$r .= texo::translate( $v ).' ';
			$r = trim( $r );
		}
		
		return $r;
	}
}

?>